package com.alameda.service.impl.media;

import cn.hutool.core.io.FileUtil;
import com.alameda.common.base.constant.MediaConstant;
import com.alameda.common.base.constant.SqlDmlConstant;
import com.alameda.common.commonpent.commonback.api.CommonBackTemplate;
import com.alameda.common.exception.BusinessException;
import com.alameda.common.utils.CollectionUtils;
import com.alameda.common.utils.ObjectUtils;
import com.alameda.common.utils.PubUtils;
import com.alameda.facade.media.AdMediaFacade;
import com.alameda.filestorage.business.service.FileStorageComponent;
import com.alameda.media.dto.AdMediaDTO;
import com.alameda.media.vo.AdMediaVO;
import com.alameda.pojo.media.AdMediaDO;
import com.alameda.service.tag.AdTagMiddleService;
import com.alameda.service.type.AdMediaMiddleService;
import com.alameda.statement.enums.AdSystemRecordEnum;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.alameda.common.base.constant.MediaConstant.FileUpload.*;
import static com.alameda.common.base.constant.MediaConstant.StringConstant.*;
import static com.alameda.common.base.constant.SqlDmlConstant.*;

/**
 * 媒体文件 业务逻辑层扩展 具体实现
 *
 * @author Lv
 * @date 2024/07/20
 */
@Slf4j
@Primary
@Service
public class AdMediaServiceImplExt extends AdMediaServiceImpl {

    @Autowired
    private AdMediaMiddleService adMediaMiddleService;

    @Autowired
    private AdTagMiddleService adTagMiddleService;

    @Autowired
    private FileStorageComponent fileStorageComponent;

    @Autowired
    private AdMediaFacade adMediaFacade;

    /**
     * 上传文件
     *
     * @param adMediaDTO 媒体文件信息
     * @return
     */
    @Override
    public AdMediaVO saveAndUploadMedia(AdMediaDTO adMediaDTO) {
        checkParamData(adMediaDTO);
        // 上传文件
        FileInfo fileInfo = fileStorageComponent.upload(adMediaDTO.getFile());
        String url = fileInfo.getUrl();

        // 获取封面
        String cover = PubUtils.isEmpty(adMediaDTO.getFileCover()) ? dealCover(adMediaDTO.getFile(), fileInfo) : adMediaDTO.getFileCover();

        String filename = PubUtils.isEmpty(adMediaDTO.getFileName()) ? adMediaDTO.getFile().getOriginalFilename() : adMediaDTO.getFileName();
        AdMediaDO adMediaDO = new AdMediaDO();
        adMediaDO.setFileUrl(url);
        adMediaDO.setFileName(filename);
        adMediaDO.setFileCover(cover);
        AdMediaDO insert = this.insert(adMediaDO);

        adMediaDTO.setId(insert.getId());
        adMediaMiddleService.saveMiddleType(adMediaDTO);
        adTagMiddleService.saveMiddleTag(adMediaDTO);
        adMediaFacade.saveRecord(AdSystemRecordEnum.MESSAGE_COUNT);

        return ObjectUtils.convert(insert, AdMediaVO.class);
    }

    /**
     * 主流视频格式
     */
    private static final List<String> VIDEO_FORMAT_LIST;

    /**
     * 初始化主流视频格式
     */
    static {
        VIDEO_FORMAT_LIST = Arrays.asList(MediaConstant.FileFormat.MP_4, MediaConstant.FileFormat.MOV, MediaConstant.FileFormat.AVI);
    }

    /**
     * 处理封面（只处理视频文件封面）
     *
     * @param multipartFile 上传文件
     * @param fileInfo      上传文件信息
     * @return
     */
    @SuppressWarnings("all")
    private String dealCover(MultipartFile multipartFile, FileInfo fileInfo) {
        String originalFilename = multipartFile.getOriginalFilename();
        int index = Optional.ofNullable(originalFilename).orElse(DOT).lastIndexOf(DOT);
        String fileFormat = originalFilename.substring(index + 1);

        return VIDEO_FORMAT_LIST.contains(fileFormat) ? getFileCover(fileInfo) : EMPTY_STRING;
    }

    /**
     * 校验上传文件
     *
     * @param adMediaDTO 媒体文件信息
     */
    private void checkParamData(AdMediaDTO adMediaDTO) {
        Long count = this.count(AdMediaDO.builder().fileName(adMediaDTO.getFile().getOriginalFilename()).build());
        if (count > 0) {
            BusinessException.throwException("已存在同名文件");
        }
    }

    /**
     * 基础路径
     */
    @Value("${machine.file.base-path}")
    private String basePath;

    /**
     * 文件存储路径
     */
    @Value("${machine.file.storage-path}")
    private String storagePath;

    /**
     * 获取视频文件封面
     *
     * @param fileInfo 上传文件信息
     * @return
     */
    private String getFileCover(FileInfo fileInfo) {
        // 获取文件对象
        File tempFile = new File(storagePath + File.separator + basePath + File.separator + fileInfo.getFilename());

        // 视频文件路径
        String videoPath = tempFile.getAbsolutePath();

        // 封面图片路径
        String saveCoverPath = System.getProperty(MediaConstant.FileUpload.SAVE_PATH)
                + File.separator + LocalDateTime.now()
                + MediaConstant.FileUpload.COVER;

        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoPath);
        try {
            grabber.start();
            Frame frame = grabber.grabImage();
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage image = converter.getBufferedImage(frame);
            ImageIO.write(image, MediaConstant.FileUpload.COVER_FORMAT, new File(saveCoverPath));
            log.info("封面图片保存成功");

            // 转换本地文件为上传文件
            MultipartFile coverMultipartFile = convertFileToMultiPart(new File(saveCoverPath));
            return fileStorageComponent.uploadFile(coverMultipartFile);
        } catch (Exception e) {
            log.error("封面处理失败:{}", e.getMessage());
            BusinessException.throwException(e.getMessage());
        } finally {
            try {
                grabber.stop();
                grabber.release();
                FileUtil.del(saveCoverPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 转换本地文件为上传文件
     *
     * @param file 文件流
     * @return
     */
    @SneakyThrows
    public MultipartFile convertFileToMultiPart(File file) {
        // 确保文件存在
        if (!file.exists() || !file.isFile()) {
            throw new IllegalArgumentException("文件不存在");
        }

        // 读取文件内容到字节数组中
        byte[] content = Files.readAllBytes(file.toPath());

        // 创建一个MockMultipartFile对象
        return new MockMultipartFile(file.getName(), file.getName(), "image/jpeg", content);
    }

    /**
     * 上传媒体文件封面
     *
     * @param adMediaDTO 媒体文件信息
     * @return
     */
    @Override
    public String uploadMediaCover(AdMediaDTO adMediaDTO) {
        String url = fileStorageComponent.uploadFile(adMediaDTO.getFile());
        log.info("封面路径:{}", url);
        return url;
    }

    /**
     * 物理层面删除已被替换文件封面
     *
     * @return
     */
    @Override
    public Boolean removeReplacedCover() {
        // 指定当前文件夹路径
        File folder = new File(storagePath + basePath);
        // 获取当前文件夹下的所有文件和文件夹
        File[] files = folder.listFiles();

        if (PubUtils.isNull(files)) {
            return Boolean.FALSE;
        }

        // 获取文件封面名称集合
        List<String> dbCoverFileNameList = listCoverFileName();

        Arrays.stream(files)
                .filter(File::isFile)
                .filter(file -> !MAC_OS_FILE_INDEX_FORMAT.equals(file.getName()))
                .filter(this::filterCoverFormatFile)
                .forEach(file -> dealRemoveCoverFile(file, dbCoverFileNameList));

        return Boolean.TRUE;
    }

    /**
     * 删除已被替换文件封面
     *
     * @param file                文件对象
     * @param dbCoverFileNameList 数据库中文件封面名称
     */
    private void dealRemoveCoverFile(File file, List<String> dbCoverFileNameList) {
        if (!dbCoverFileNameList.contains(file.getName())) {
            // 封面图片已被替换 清理释放存储空间
            FileUtil.del(file);
        }
    }

    /**
     * 判断当前文件是否符合封面文件格式
     *
     * @param file 文件对象
     * @return
     */
    private boolean filterCoverFormatFile(File file) {
        // 获取文件名
        String fileName = file.getName();
        // 获取文件格式
        String fileFormat = getFileFormat(fileName);
        // 是否为图片格式
        return PubUtils.isNotEmpty(fileFormat) && (PNG.equalsIgnoreCase(fileFormat) || JPG.equalsIgnoreCase(fileFormat));
    }

    /**
     * 获取文件格式
     *
     * @param fileName 文件名称
     * @return
     */
    private String getFileFormat(String fileName) {
        int i = fileName.lastIndexOf('.');
        String fileFormat = "";
        if (i > 0) {
            fileFormat = fileName.substring(i + 1);
        }
        return fileFormat;
    }

    /**
     * 获取文件名称封面集合
     *
     * @return
     */
    private List<String> listCoverFileName() {
        // 读取所有文件数据
        List<AdMediaDO> adMediaDOList = this.list();
        return adMediaDOList.stream().map(item -> getCoverNameByUrl(item.getFileCover())).collect(Collectors.toList());
    }

    /**
     * 文件存储服务器地址
     */
    @Value("${machine.file.domain}")
    private String address;

    /**
     * 通过文件封面路径获取封面名称
     *
     * @param fileCoverUrl 文件封面路径获
     * @return
     */
    private String getCoverNameByUrl(String fileCoverUrl) {
        return fileCoverUrl.replace(address + basePath, EMPTY_STRING);
    }

    /**
     * 物理层面移除已逻辑删除文件
     *
     * @return
     */
    @Override
    public Boolean removeLogicDeletedFile() {
        // 物理层面删除已被替换文件封面
        removeReplacedCover();

        List<AdMediaDO> adMediaRemoveList = findLogicDeletedData();
        log.info("已逻辑删除数据:{}", JSONObject.toJSONString(adMediaRemoveList));
        if (PubUtils.isEmpty(adMediaRemoveList)) {
            return Boolean.FALSE;
        }

        List<String> urlList = adMediaRemoveList.stream().map(AdMediaDO::getFileUrl).collect(Collectors.toList());
        List<AdMediaDO> adMediaRepeatList = this.list(Wrappers.lambdaQuery(AdMediaDO.class).in(AdMediaDO::getFileUrl, urlList));
        adMediaRepeatList = Optional.ofNullable(adMediaRepeatList).orElse(CollectionUtils.newArrayList());

        // 移除文件
        dealRemoveFile(adMediaRemoveList, adMediaRepeatList);
        String ids = adMediaRemoveList.stream().map(item -> item.getId().toString()).collect(Collectors.joining(COMMA));

        // 删除无效媒体数据
        String deleteMediaSql = DELETE_AD_MEDIA + LEFT_BRACKET + ids + RIGHT_BRACKET;
        Integer countDeleteMediaSql = commonBackTemplate.executeDelete(deleteMediaSql);
        log.info("处理无效媒体数据量:{}", countDeleteMediaSql);

        // 删除无效媒体标签关联数据
        String deleteTagSql = DELETE_AD_TAG_MIDDLE + LEFT_BRACKET + ids + RIGHT_BRACKET;
        Integer countDeleteTagSql = commonBackTemplate.executeDelete(deleteTagSql);
        log.info("处理无效媒体标签数据量:{}", countDeleteTagSql);

        return Boolean.TRUE;
    }

    /**
     * 物理删除文件
     *
     * @param adMediaRemoveList 已逻辑删文件信息
     * @param adMediaRepeatList 重复文件信息
     */
    private void dealRemoveFile(List<AdMediaDO> adMediaRemoveList, List<AdMediaDO> adMediaRepeatList) {
        List<String> adMediaRepeatUrlList = adMediaRepeatList.stream().map(AdMediaDO::getFileUrl).collect(Collectors.toList());
        adMediaRemoveList.forEach(adMediaDO -> {
            if (!adMediaRepeatUrlList.contains(adMediaDO.getFileUrl())) {
                // 非重复数据 物理删除
                if (PubUtils.isNotEmpty(adMediaDO.getFileCover())) {
                    String fileCoverPath = getFilePath(adMediaDO.getFileCover());
                    FileUtil.del(new File(fileCoverPath));
                    log.info("成功删除图片文件:{}", fileCoverPath);
                }

                if (PubUtils.isNotEmpty(adMediaDO.getFileUrl())) {
                    String mediaPath = getFilePath(adMediaDO.getFileUrl());
                    FileUtil.del(new File(mediaPath));
                    log.info("成功删除媒体文件:{}", mediaPath);
                }

            }
        });
    }

    /**
     * 获取文件绝对路径
     *
     * @param url 文件访问url
     * @return
     */
    private String getFilePath(String url) {
        String relativePath = url.replace(address, EMPTY_STRING);
        String absolutePath = storagePath + relativePath;
        log.info("当前文件路径：{}", absolutePath);
        return absolutePath;
    }

    @SuppressWarnings("all")
    @Autowired
    private CommonBackTemplate commonBackTemplate;

    /**
     * 读取已删除媒体文件
     *
     * @return
     */
    @SuppressWarnings("all")
    private List<AdMediaDO> findLogicDeletedData() {
        List list = commonBackTemplate.executeFind(SqlDmlConstant.SELECT_AD_MEDIA_DELETED);
        return ObjectUtils.convertList(list, AdMediaDO.class);
    }
}
